#include "split.hpp"
#include <system.h>
#include <tree.h>
#include <cgraph.h>
#include <context.h>
#include <diagnostic.h>
#include <stringpool.h>
#include <expr.h>
#include <coretypes.h>
#include <print-tree.h>
#include <hooks.h>
#include <attribs.h>
#include <queue>
static attribute_spec attribute_sw_64_host = {"sw_64_host", 0, 0, false, false, false, NULL, false};
static attribute_spec attribute_sw_64_slave = {"sw_64_slave", 0, 0, false, false, false, NULL, false};

namespace {
  const pass_data pass_data_sw_64_split =
    {
      SIMPLE_IPA_PASS,		/* type */
      "sw_64_split",		/* name */
      OPTGROUP_NONE,		/* optinfo_flags */
      TV_NONE,			/* tv_id */
      ( PROP_ssa | PROP_cfg ),	/* properties_required */
      0,				/* properties_provided */
      0,				/* properties_destroyed */
      0,				/* todo_flags_start */
      TODO_update_ssa		/* todo_flags_finish */
    };
};

class pass_sw_64_split : public simple_ipa_opt_pass
{
  const char *output_list, *host_flag, *slave_flag;
  bool slave_explicit;
  bool has_slave;
  bool has_host;
public:
  pass_sw_64_split (gcc::context *ctxt, const char *output_list, const char *host_flag, const char *slave_flag, bool slave_explicit)
    : simple_ipa_opt_pass (pass_data_sw_64_split, ctxt),
      output_list(output_list),
      slave_explicit(slave_explicit),
      host_flag(host_flag),
      slave_flag(slave_flag)
  {}

  /* opt_pass methods: */
  virtual bool gate (function *){
    return true;
  }
  void externalize(){
    cgraph_node *node;
    has_slave = false;
    has_host = false;
    if (slave_explicit && TARGET_SW_SLAVE){
      std::queue<cgraph_node *> slave_nodes;
      FOR_EACH_FUNCTION(node) {
        auto attr_slave = lookup_attribute("sw_64_slave", DECL_ATTRIBUTES(node->decl));
        if (attr_slave) {
          slave_nodes.push(node);
        }
      }

      while (!slave_nodes.empty()){
        node = slave_nodes.front();
        slave_nodes.pop();
        for (cgraph_edge *e = node->callees; e; e = e->next_callee){
          if (!lookup_attribute("sw_64_slave", DECL_ATTRIBUTES(e->callee->decl))) {
            slave_nodes.push(e->callee);
            DECL_ATTRIBUTES(e->callee->decl) = make_attribute("sw_64_slave", "", DECL_ATTRIBUTES(e->callee->decl));
          }
        }
      }
    }
    FOR_EACH_FUNCTION(node) {
      auto attr_host = lookup_attribute("sw_64_host", DECL_ATTRIBUTES(node->decl));
      auto attr_slave = lookup_attribute("sw_64_slave", DECL_ATTRIBUTES(node->decl));
      
      if (attr_slave || !attr_host && !slave_explicit) {
        has_slave = true;
      }
      if (TARGET_SW_HOST && attr_slave) {
        DECL_EXTERNAL(node->decl) = 1;
        has_host = true;
      }
      if (slave_explicit){
        if (TARGET_SW_SLAVE && !attr_slave) {
          DECL_EXTERNAL(node->decl) = 1;
        }
      } else {
        if (TARGET_SW_SLAVE && attr_host) {
          DECL_EXTERNAL(node->decl) = 1;
        }
      }
    }
    if (has_slave && slave_flag) {
      fclose(fopen(slave_flag, "w"));
    }
    if (has_host && host_flag){
      fclose(fopen(host_flag, "w"));
    }
  }
  void globalize(){
    FILE *local_list = fopen(output_list, "w");
    symtab_node *node;
    int ptr = 0;
    
    FOR_EACH_SYMBOL(node){
      if (!TREE_PUBLIC(node->decl)){
        TREE_PUBLIC(node->decl) = 1;
        fprintf(local_list, "%s\n", IDENTIFIER_POINTER(DECL_ASSEMBLER_NAME(node->decl)));
      }
    }
    fclose(local_list);
  }
  virtual unsigned int execute (function *) {
    externalize();
    if (has_slave)
      globalize();
    return 0;
    //warning(0, "test"); return 0;
  }
};

class builtin_slave_func : public nohp_builtin_function {
public:
  builtin_slave_func() {
    name = "__builtin_slave_func";
  }
  virtual tree type(){
    return build_function_type_list(ptr_type_node, const_ptr_type_node, NULL_TREE);
  }
  virtual rtx expand(tree exp, rtx target, rtx subtarget, machine_mode mode, int ignore){
    call_expr_arg_iterator iter;
    auto op = expand_expr(first_call_expr_arg(exp, &iter), NULL_RTX, VOIDmode, EXPAND_CONST_ADDRESS);
      
    if (!op || GET_CODE(op) != SYMBOL_REF){
      error("Only function references can be used in __builtin_slave_func");
    } else {
      const char *new_name = XSTR(op, 0);
      if (strncmp(new_name, "slave_", 6))
        new_name = ACONCAT(("slave_", XSTR(op, 0), NULL));
      tree new_id = get_identifier(new_name);
      //rtx x = gen_rtx_SET(target, gen_rtx_SYMBOL_REF(DImode, IDENTIFIER_POINTER(new_id)));
      if (!target)
        target = gen_reg_rtx(Pmode);
      emit_move_insn(target, gen_rtx_SYMBOL_REF(DImode, IDENTIFIER_POINTER(new_id)));
      return target;
    }
    gcc_unreachable();
  }
};

void sw_64_split::add_passes() {
  const char *local_list = TARGET_SW_SLAVE ? "slave-local.list" : "host-local.list";
  const char *host_flag = nullptr, *slave_flag = nullptr;
  bool slave_explicit = true;
  for (int i = 0; i < nohp::plugin_info->argc; i ++){
    if (!strcmp(nohp::plugin_info->argv[i].key, "local-list"))
      local_list = xstrdup(nohp::plugin_info->argv[i].value);
    else if (!strcmp(nohp::plugin_info->argv[i].key, "slave-flag")){
      slave_flag = xstrdup(nohp::plugin_info->argv[i].value);
    }
    else if (!strcmp(nohp::plugin_info->argv[i].key, "host-flag"))
      host_flag = xstrdup(nohp::plugin_info->argv[i].value);
    else if (!strcmp(nohp::plugin_info->argv[i].key, "slave-explicit")){
      if (!strcmp(nohp::plugin_info->argv[i].value, "true")) {
        slave_explicit = true;
      } else if (!strcmp(nohp::plugin_info->argv[i].value, "false")) {
        slave_explicit = false;
      } else {
        error("slave-explicit argument error, should be true or false");
      }
    }
  }

  nohp::register_pass(new pass_sw_64_split(g, local_list, host_flag, slave_flag, slave_explicit), "simdclone", 1, PASS_POS_INSERT_AFTER);
  targetm.function_attribute_inlinable_p = hook_bool_const_tree_true;
}

void sw_64_split::add_builtins() {
  nohp::add_builtin(new builtin_slave_func());
}

void sw_64_split::add_attributes(){
  nohp::add_attribute(&attribute_sw_64_host);
  nohp::add_attribute(&attribute_sw_64_slave);
}
